<?PHP
/*
 * $Id: barge-svn.classes.php 139 2009-12-16 15:29:56Z namba0219 $
 * =============================================================================
 * Barge - web application framework on PHP5
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * =============================================================================
 * For more information, see http://barge.googlecode.com/
 */
namespace barge\svn;

use \webdav_client;
use \phpsvnclient;

use barge\ClassLoader;
use barge\System;
use barge\logging\LogFactory;
use barge\utils\ArrayList;
use barge\utils\Bean;
use barge\utils\Timestamp;
use barge\utils\OutputBuffer;

class SVNConstant
{
	const HEAD_REVISION = -1;
	const FILE = 1;
	const DIR = 2;
}

/**
 *  
 * @author Kenji Namba
 * @version 1.0 2008/09/18
 */
class SVNClient
{
	/**
	 * @var string
	 */
	private $reposuri;
	
	/**
	 * @var Logger
	 */
	private $log;
	
	/**
	 * Enter description here...
	 *
	 * @param string $reposuri Windows の場合はディレクトリーパス
	 * 			の大文字・小文字を区別しますので注意してください。大文字・小文字が異なる
	 * 			ワーキングディレクトリを指定するとエラーになります。
	 * 			例）file:///C:/sample, svn://localohst/, http://localohst/
	 * @param string $user
	 * @param string $password
	 * @return SVNClient
	 */
	public static function connect($reposuri, $user = null, $password = null) 
	{
		return new self($reposuri, $user, $password);
	} 

	/**
	 * 
	 * @param string $reposuri
	 * @param string $user
	 * @param string $password
	 */
	protected function __construct($reposuri, $user, $password)
	{
		$this->log = LogFactory::getLogger();
		$this->reposuri = $reposuri; 
		if ($user) 
			$this->setUserName($user);
		if ($password)
			$this->setPassword($password);
	}
	
	/**
	 * ユーザー名を設定します	
	 * @param string $user
	 */
	public function setUserName($user) 
	{
		svn_auth_set_parameter(SVN_AUTH_PARAM_DEFAULT_USERNAME, $user);
	}
	
	/**
	 * パスワードを設定する
	 * @param string $password
	 */
	public function setPassword($password) 
	{
		svn_auth_set_parameter(SVN_AUTH_PARAM_DEFAULT_PASSWORD, $password);
	}
	
	/**
	 * @param string $uri
	 * @param string $path
	 * @return string
	 */
	protected function normalizePath($uri, $path)
	{
		$end = substr($uri, -1, 1);
		$start = $path[0];
		if ($end === '/' && $start === '/') {
			return $uri.substr($path, 1);
		} else if ($end !== '/' && $start !== '/') {
			return $uri.'/'.$path;
		} else {
			return $uri.$path;
		}
	}
	
	/**
	 * @param string $path
	 * @param int $version
	 * @param bool $recurse
	 * @return ArrayList {NodeDescriptor}
	 */
	public function getFile($path = '/', $version = null) 
	{
		$uri = $this->normalizePath($this->reposuri, $path);
		
		$this->log->debug('svn_cat : '. $uri);
		return svn_cat($uri, 
			($revision !== null ? $revision : SVNConstant::HEAD_REVISION));
	}
	
	/**
	 * @param string $dirpath
	 * @param int $version
	 * @param bool $recurse
	 * @return ArrayList {NodeDescriptor}
	 */
	public function listFiles($dirpath = '/', $version = null, $recurse = false) 
	{
		$uri = $this->normalizePath($this->reposuri, $dirpath);
		$this->log->debug('svn ls : '.$uri);
		$list = svn_ls($uri, 
			($revision !== null ? $revision : SVNConstant::HEAD_REVISION), $recurse);
		$array = new ArrayList();
		if ($list === false ) {
			$message = 'Fail to svn ls.';
			throw new SVNException($message);
		}

		foreach ($list as $item) {
			$type = ($item['type'] === 'file' ? SVNConstant::FILE : SVNConstant::DIR);
			$descriptor = new NodeDescriptor($item['name'], $type, 
				($dirpath === '/' ? $dirpath.$item['name'] : $dirpath.'/'.$item['name']));
			$descriptor->setRevision($item['created_rev']);
			$descriptor->setLastModified(new Timestamp($item['time_t']));
			$descriptor->setSize(isset($item['size']) ? $item['size'] : null);
/*
 ["created_rev"]=>
  int(29)
  ["last_author"]=>
  string(5) "Namba"
  ["size"]=>
  int(49853)
  ["time"]=>
  string(12) "Nov 23 19:02"
  ["time_t"]=>
  int(1227434529)
  ["name"]=>
  string(17) "2005 9 4 1142.jpg"
  ["type"]=>
  string(4) "file"

        [created_rev] => 最後に変更されたリビジョン番号
        [last_author] => 最後に変更した人の名前
        [size] => ファイルのバイト数
        [time] => 最終更新日時を、その古さによって 'M d H:i'
                  あるいは 'M d Y' のいずれかの形式で表したもの
        [time_t] => 最終更新日時の unix タイムスタンプ
        [name] => ファイル/ディレクトリの名前
        [type] => 'file' あるいは 'dir' のいずれか

 */
//$this->log->debug(
//OutputBuffer::capture(function() use ($w) {
//			var_dump($w);
//		}));

			$array->add($descriptor);
		}
		return $array;
	}

	/**
	 * @param string $path	
	 */
	public function makeDir($path) 
	{
		$uri = $this->normalizePath($this->reposuri, $path);
		$ret = svn_mkdir($uri);
		$this->log->debug('svn mkdir : '.$uri);
		if ($ret === false ) {
			$message = 'Fail to svn delete. ' . $path;
			throw new SVNException($message);
		}
	}


	public function log() 
	{
	}
	
	/**
	 * @param string $path
	 * @param string $force 強制的に削除を行うかどうか	
	 */
	public function delete($path, $force = false) 
	{
		$uri = $this->normalizePath($this->reposuri, $path);
		$this->log->debug(svn_auth_get_parameter(SVN_AUTH_PARAM_DEFAULT_USERNAME));
		$this->log->debug(svn_auth_get_parameter(SVN_AUTH_PARAM_DEFAULT_PASSWORD));
		$this->log->debug('svn delete : '.$uri);
		$ret = svn_delete($uri, true);
// bool|array svn_delete
//var_dump($ret);
//$ret = svn_delete($uri.'/xxxxxxxxxxxxxxxxxx');
//var_dump($ret);
//$ret = svn_delete($uri.'/xxxxxxxxxxxxxxxxxx');
		//$this->log->debug('svn delete : '.($ret ));
		if ($ret === false ) {
			$message = 'Fail to svn delete. ' . $path;
			throw new SVNException($message);
		}
	}

	/**
	 * 作業コピーディクトリにチェックアウト
	 * 
	 * @param string $workdir ワークディレクトリ	
	 * @return SVNLocal
	 */
	public function checkout($workdir, $revision = null) 
	{
		$uri = $this->normalizePath($this->reposuri, $path);

		// SVN_NON_RECURSIVE, SVN_IGNORE_EXTERNALS
		$ret = svn_checkout($this->reposuri, $workdir, 
			($revision !== null ? $revision : SVNConstant::HEAD_REVISION));
		if ($ret === false) {
			$message = 'Fail to checkout. ' . $workdir;
			throw new SVNException($message);
		}
		return new SVNLocal($workdir);
	}
	
	/**
	 * @param string $path
	 * @return SVNRepos
	 */
	public function openRepos()
	{
		if (strpos($this->reposuri, 'file:///') !== 0) {
			$message = 'Cant open repository, URL must local access "file:///"';
			throw new SVNException($message);
		}
		return SVNRepos::open(substr($this->reposuri, 8));
	}
}

class SVNException extends Exception 
{
}

/**
 * Enter description here...
 *
 */
class SVNRepos 
{
	/**
	 * @var resource type of php_svn_repos
	 */
	private $repos;

	/**
	 * @param string $repospath レポジトリーファイルシステムのパス 
	 */
	public static function open($repospath)
	{
		return new self($repospath);
	}
	
	/**
	 * @param string $repospath
	 * @throws SVNException
	 */
	private function __construct($repospath)
	{
		$this->repos = svn_repos_open($repospath);
		if ($this->repos === false) {
			$message = 'Cant open repository : '.$repospath;
			throw new SVNException($message);
		}
	}

	/**
	 * @param string $repospath
	 * @throws SVNException
	 */
	public function fs()
	{
		return new SVNFS($this->repos);
	}
	
}

class SVNFS 
{
	/**
	 * @var resource type of php_svn_fs
	 */
	private $fs;
	
	/**
	 * @param resource $repos type of php_svn_repos
	 * @throws SVNException
	 */
	public function __construct($repos)
	{
		$this->fs = svn_repos_fs($repos);
	}
	
	/**
	 * 最終リビジョンを取得する 
	 * @return int
	 */
	public function getYoungestRevision()
	{
		return svn_fs_youngest_rev($this->fs);
	}
	
	/** 
	 * @param $revision
	 * @return SVNReposTxn
	 */
	public function beginTxn($revision = null)
	{
		return new SVNReposTxn(
			$this->fs, 
			($revision !== null ? $revision : SVNConstant::HEAD_REVISION));
	}
	
	
}

class SVNReposTxn
{
	/**
	 * @var resource type of php_svn_repos_fs_txn
	 */
	private $fstxn;
	
	/**
	 * @param resource $fs type of php_svn_fs
	 * @param int $revision
	 * @throws SVNException
	 */
	public function __construct($fs, $revision = null)
	{
		$this->fstxn = svn_fs_begin_txn2($fs, 
			($revision !== null ? $revision : SVNConstant::HEAD_REVISION));
	}
	
	/**
	 * @return SVNFSRoot
	 */
	public function root() 
	{
		return new SVNFSRoot($this->fstxn);
	}
//svn_fs_abort_txn
//php_svn_fs_root svn_fs_txn_root(php_svn_repos_fs_txn txn)
//int 	svn_repos_fs_commit_txn(php_svn_repos_fs_txn txn)		// return リビジョン番号
}

class SVNFSRoot
{
	/**
	 * @var resource type of php_svn_fs_root
	 */
	private $fsroot;
	
	/**
	 * @param resource $fsroot type of php_svn_repos_fs_txn
	 */
	public function __construct($fstxn)
	{
		$this->fsroot = svn_fs_txn_root($fstxn);
	}
}

/**
 * @see Bean
 */
class StatusDescriptor extends Bean 
{
//	const DIR = 1;
//	const FILE = 2;
	
	/**
	 * @var int
	 */
	private $type;
	
	/**
	 * @var int
	 */
	private $name;
	
	/**
	 * @var int
	 */
	private $path;

	/**
	 * @param string $name
	 * @param int $type
	 * @param string $path
	 */
	public function __construct($name, $type, $path)
	{
		$this->name = $name;
		$this->type = $type;
		$this->path = $path;
	}
	
	/**
	 * @return string
	 */
	public function getPath()
	{
		return $this->path;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @return int
	 */
	public function getType()
	{
		return $this->type;
	}
}

/**
 * @see Bean
 */
class NodeDescriptor extends Bean 
{

	/**
	 * @var string
	 */
	private $name;
	
	/**
	 * @var int 
	 */
	private $type = 0;
	
	/**
	 * @var string
	 */
	private $path;

	/**
	 * @var Timestamp
	 */
	private $lastModified;
	
	/**
	 * @var string
	 */
	private $lockOwner = null;
	
	/**
	 * @var int
	 */
	private $revision = -1;
	
	/**
	 * @var int
	 */
	private $size = -1;
	
	/**
	 * @param string $name
	 * @param int $type
	 * @param string $path
	 */
	public function __construct($name, $type, $path)
	{
		$this->name = $name;
		$this->type = $type;
		$this->path = $path;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}
	
	public function getType()
	{
		return $this->type;
	}
	
	/**
	 * @return string
	 */
	public function getPath()
	{
		return $this->path;
	}
	
	public function setLockOwner($owner)
	{
		$this->lockOwner = $owner;
	}
	
	/**
	 * @return string
	 */
	public function getLockOwner()
	{
		return $this->lockOwner;
	}
	
	public function getLastModified()
	{
		return $this->lastModified;
	}

	public function setLastModified($value)
	{
		$this->lastModified = $value;
	}
	
	public function getRevision()
	{
		return $this->revision;
	}

	public function setRevision($value)
	{
		$this->revision = $value;
	}
	
	public function getSize()
	{
		return $this->size;
	}

	public function setSize($value)
	{
		$this->size = $value;
	}
	
	public function isDir()
	{
		return ($this->type == SVNConstant::DIR);
	}
}


class SVNLocal
{
	/** 
	 * @var Logger
	 */
	private $log;
	
	/**
	 * 作業コピーディレクトリ 
	 * @var string
	 */
	private $workdir;
	
	/**
	 * @param string $workdir 作業コピーディレクトリ
	 * @return SVNLocal 
	 */
	public static function open($workdir)
	{ 
		return new self($workdir);
	}

	/**
	 * @param string $workdir 作業コピーディレクトリ 
	 */
	protected function __construct($workdir)
	{
		$this->log = LogFactory::getLogger();
		$this->workdir = $workdir;
	}

	/**
	 * ローカルファイルを更新します 
	 * @param string $path 
	 * @param int $revision
	 * @param bool $recurse
	 * @return int リビジョン番号 
	 */
	public function update($path = '/', $revision = null, $recurse = false)
	{
		$filepath = $this->workdir . $path;
		if ($this->log->isDebugEnabled()) {
			$this->log->debug('update : ' . $filepath);
		}
		$ret = svn_update(
			$filepath, 
			($revision === null ? SVNConstant::HEAD_REVISION : $revision), 
			$recurse);
		return $ret;
	}

	/**
	 * ローカルディレクトリのファイル一覧を取得します 
	 * @param bool $recurse
	 * @return ArrayList
	 */
	public function listStatus($path = '/', $recurse = false)
	{
		if ($path === '/')
			$path = '';
			
		$filepath = $this->workdir . $path;
		if ($this->log->isDebugEnabled()) {
			$this->log->debug('status : ' . $filepath);
		}
		$nodes = svn_status($filepath, $recurse ? SVN_ALL : (SVN_ALL | SVN_NON_RECURSIVE));
		$list = new ArrayList();
		$path = null;
		foreach ($nodes as $node) {
			if ($filepath === $node['path']) continue;
			$path = substr($node['url'], strlen($node['repos']));
//var_dump($nodes);
			$list->add(new StatusDescriptor(basename($path), $node['kind'], $path));
		}
		return $list;
		/*
    ["path"]=>
    string(86) "C:/projects/barge/500_/barge1.2/webapps/svnclient/work/modules/svnclient/svntemp/media"
    ["text_status"]=>
    int(3)
    ["repos_text_status"]=>
    int(1)
    ["prop_status"]=>
    int(1)
    ["repos_prop_status"]=>
    int(1)
    ["locked"]=>
    bool(false)
    ["copied"]=>
    bool(false)
    ["switched"]=>
    bool(false)
    ["name"]=>
    string(0) ""
    ["url"]=>
    string(25) "file:///C:/svnroot3/media"
    ["repos"]=>
    string(19) "file:///C:/svnroot3"
    ["revision"]=>
    int(5)
    ["kind"]=>
    int(2)
    ["schedule"]=>
    int(0)
    ["cmt_date"]=>
    int(1228580006)
    ["cmt_rev"]=>
    int(4)
    ["cmt_author"]=>
    string(5) "Namba"

		 * 
		 */

	}
}

/*

 my $repos = SVN::Repos::create("/path/to/fs", undef, undef, undef, undef);
 my $txn = $repos->fs->begin_txn($fs->youngest_rev);

$txn->root->make_dir("trunk");
$txn->root->make_dir("tags");
$txn->root->make_dir("branches");

$txn->root->make_file('README');

my $stream = $txn->root->apply_text('README', undef);
print $stream &lt;&lt;EOF;
This repository now has trunk, tags, and branches directories
EOF

close $stream;
$txn->commit;
*/


?>